package com.imon.web.codesphere;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.tmatesoft.svn.core.SVNException;

import com.google.code.morphia.Key;
import com.imon.web.codesphere.data.FileSource;
import com.imon.web.codesphere.data.RepositoryInfo;
import com.imon.web.codesphere.data.RevisionInfo;

@Component
public class RepositoryGateway {
	private static final Log LOG = LogFactory.getLog(RepositoryGateway.class);
	public static final int DEFAULT_NUMBER_OF_REVISIONS = 50;

	@Resource
	private MongoDatabase database;

	@Value("#{properties['revisions']?: T(com.imon.web.codesphere.RepositoryGateway).DEFAULT_NUMBER_OF_REVISIONS}")
	private int numberOfRevisionsToFetch;

	@Value("${binary.file.extensions:\"gz;war;jar;ear;zip\"}")
	private String binaryFileExtensions;

	/*
	 * Sync Repositories every hour
	 */
	@Scheduled(fixedRate = 1000 * 60 * 60)
	public void processAllRepositories() {
		LOG.debug("processAllRepositories()");
		final Collection<RepositoryInfo> allRepos = database.loadAll(RepositoryInfo.class);
		for (final RepositoryInfo repo : allRepos) {
			try {
				if (repo.getLastRevision() == 0) {
					LOG.debug(String.format("Repo: %s is still on revision %s therefore not processing it",
							repo.getLocation(), repo.getLastRevision()));
				} else {
					processRepository(repo);
				}
			} catch (final SVNException e) {
				LOG.error(String.format("Error processing: %s", repo.getLocation()), e);
			}
		}
	}

	public void processRepository(final RepositoryInfo localRepositoryInfo) throws SVNException {
		LOG.debug(String.format("processRepository(%s)", localRepositoryInfo));
		final SubversionCodeRepository codeRepository = new SubversionCodeRepository(localRepositoryInfo.clone());
		codeRepository.setBinaryExtensions(binaryFileExtensions);

		long startRevision = 0;
		final long endRevision = codeRepository.getLastRevision();

		if (localRepositoryInfo.getLastRevision() != 0) {
			if (codeRepository.getLastRevision() > localRepositoryInfo.getLastRevision()) {
				startRevision = localRepositoryInfo.getLastRevision() + 1;
			} else {
				LOG.debug(String
						.format("Not processing (%s) as it is already up to revision (%s) along with remote repository revision (%s)",
								localRepositoryInfo.getLocation(), localRepositoryInfo.getLastRevision(),
								codeRepository.getLastRevision()));
				return;
			}
		}

		svnLog(codeRepository, startRevision, endRevision);
		saveRevisions(codeRepository, localRepositoryInfo.getLocation(), startRevision, endRevision);

		// final update the local final revision after we final fetched all the
		// final logs
		database.save(codeRepository.getRepositoryInfo());
		codeRepository.close();
	}

	private void svnLog(final SubversionCodeRepository codeRepository, final long startRevision, final long endRevision) {
		LOG.debug(String.format("svnLog(%s, %s)", startRevision, endRevision));

		final RepositoryInfo repositoryInfo = codeRepository.getRepositoryInfo();

		LOG.debug(String.format("Number of revisions to fetch: %s", numberOfRevisionsToFetch));

		long currentRevision = startRevision;
		while ((currentRevision + numberOfRevisionsToFetch) < repositoryInfo.getLastRevision()) {
			database.saveAll(codeRepository.log(currentRevision, currentRevision + numberOfRevisionsToFetch));
			currentRevision += (numberOfRevisionsToFetch + 1);
		}
		database.saveAll(codeRepository.log(currentRevision, endRevision));
	}

	private void saveRevisions(final SubversionCodeRepository codeRepository, final String repositoryLocation,
			final long startRevision, final long endRevision) {
		LOG.debug(String.format("saveRevision(%s, %s, %s)", repositoryLocation, startRevision, endRevision));

		final Map<String, Object> criteria = new HashMap<String, Object>() {
			{
				put("repositoryUrl =", repositoryLocation);
				put("revision >=", startRevision);
				put("revision <=", endRevision);

			}
		};

		final Collection<RevisionInfo> revisionHistory = database.loadAll(RevisionInfo.class, criteria);

		codeRepository.processRevisionFiles(revisionHistory, new Closure<FileSource, Key<FileSource>>() {
			@Override
			public Key<FileSource> handle(final FileSource fileSource) {
				return database.save(fileSource);
			}
		});

	}

	public RepositoryInfo saveRepository(final RepositoryInfo repositoryInfo) {
		LOG.debug(String.format("saveRepository(%s)", repositoryInfo));
		final SubversionCodeRepository codeRepository = new SubversionCodeRepository(repositoryInfo.clone());
		final RepositoryInfo remoteRepositoryInfo = codeRepository.getRepositoryInfo();

		final RepositoryInfo existingRepository = database.loadByPrimaryKey(RepositoryInfo.class,
				remoteRepositoryInfo.getLocation());

		// so that we only update the repository details without overriding the
		// version number
		if (existingRepository != null && existingRepository.getLastRevision() > 0) {
			remoteRepositoryInfo.setLastRevision(existingRepository.getLastRevision());
		} else {
			remoteRepositoryInfo.setLastRevision(0);
		}

		database.save(remoteRepositoryInfo);
		codeRepository.close();

		return remoteRepositoryInfo;
	}

	public RepositoryInfo addRepository(final String repositoryTitle, final String repositoryUrl,
			final String repositoryUsername, final String repositoryPassword) throws SVNException {
		LOG.debug(String.format("addRepository(%s, %s)", repositoryTitle, repositoryUrl));

		if (StringUtils.isEmpty(repositoryTitle) || StringUtils.isEmpty(repositoryUrl)) {
			throw new IllegalArgumentException("Repository title or url is null or empty");
		}

		final RepositoryInfo repositoryInfo = new RepositoryInfo();
		repositoryInfo.setRepositoryTitle(repositoryTitle);
		repositoryInfo.setLocation(repositoryUrl);
		repositoryInfo.setUsername(repositoryUsername);
		repositoryInfo.setPassword(repositoryPassword);
		repositoryInfo.setLastRevision(0);

		return saveRepository(repositoryInfo);

		// final RepositoryInfo remoteRepositoryInfo =
		// saveRepository(repositoryInfo);
		// processRepository(remoteRepositoryInfo);
	}
}
